#include "serviceworld.h"

#include <limits>

#include "debugdraw.h"

namespace infinity {

/*static*/ const ServiceKey ServiceWorld::Key = ServiceKey("Service::World");

/*virtual*/ ServiceWorld::~ServiceWorld()
{
	dwAssert(m_areasOfInterest.empty(), "There are still registered listeners");
}

void ServiceWorld::registerAreaOfInterest(const AreaOfInterest* area)
{
	dwAssert(area != NULL, "Invalid parameter: area == NULL");
	dwAssert(std::find(m_areasOfInterest.begin(), m_areasOfInterest.end(), area) == m_areasOfInterest.end(), "Invalid parameter: area is already registered");

	m_areasOfInterest.push_back(area);
}

void ServiceWorld::unregisterAreaOfInterest(const AreaOfInterest* area)
{
	dwAssert(area != NULL, "Invalid parameter: area == NULL");
	dwAssert(std::find(m_areasOfInterest.begin(), m_areasOfInterest.end(), area) != m_areasOfInterest.end(), "Invalid parameter: area is not registered");

	m_areasOfInterest.erase(std::find(m_areasOfInterest.begin(), m_areasOfInterest.end(), area));
}

void ServiceWorld::renderDebug()
{
	super::renderDebug();

	AABBox< dw::int32, 3 > areaBounds;
	AABBox< dw::int32, 3 > sectorBounds;

	for (AreasCollection::const_iterator areaIt = m_areas.begin(); areaIt != m_areas.end(); ++areaIt)
	{
		WorldArea* area = *areaIt;

		/*
		areaBounds.min = area->getId() * Vector3i(WorldArea::SideCells, WorldArea::SideCells, WorldArea::SideCells);
		areaBounds.max = areaBounds.min + Vector3i(WorldArea::SideCells, WorldArea::SideCells, WorldArea::SideCells);

		DebugDraw::render(dw::graphics::Colorf::Blue, areaBounds);
		*/
		
		const WorldArea::SectorsCollection& sectors = area->getSectors();
		WorldArea::SectorsCollection::const_iterator sectorsEnd = sectors.end();

		for (WorldArea::SectorsCollection::const_iterator sectorIt = sectors.begin(); sectorIt != sectorsEnd; ++sectorIt)
		{
			WorldSector* sector = *sectorIt;
			dw::graphics::Colorf color;

			color.r = 0.0f;
			color.g = (sector->age == 0 ? 1.0f - std::min(sector->priority, 1.0f) : 0.0f);
			color.b = 0.0f;

			sectorBounds.min = (dw::int32)WorldSector::SideCells * sector->getId();
			sectorBounds.max = (dw::int32)WorldSector::SideCells * (sector->getId() + Vector3i(1, 1, 1));

			DebugDraw::render(color, sectorBounds);
		}
	}

	for (AreasOfInterestCollection::const_iterator areaIt = m_areasOfInterest.begin(); areaIt != m_areasOfInterest.end(); ++areaIt)
	{
		(*areaIt)->getAABBox(areaBounds);

		DebugDraw::render(dw::graphics::Colorf::Red, areaBounds);
	}
}

/*virtual*/ Service::State ServiceWorld::onInitializeSimulation()
{
	/*
	State state = super::onInitializeSimulation();

	if (state != State_Initialized)
	{
		return state;
	}

	m_planet = Planet::load("./test.ini");
	if(!m_planet)
		m_planet = new Planet(512, 64, -64);

	initializeAreas();
	initializeSectors();
	*/
	return State_Initialized;
}

/*virtual*/ void ServiceWorld::onUpdateSimulation(dw::float32 deltaT)
{
	/*
	AABBox< dw::int32, 3 > areaBoundsCells;
	AABBox< dw::int32, 3 > sectorBoundsCells;
	
	WorldArea* worldArea;

	Vector3i areaId;
	Vector3i sectorId;

	Vector3i sectorStart;
	Vector3i sectorEnd;

	WorldSector* sector;

	bool needsSort = false;
	dw::float32 lowestCachedPrio = std::numeric_limits< dw::float32 >::max();

	dwAssert(m_cache.size() == 0, "Invalid internal state");

	for (AreasCollection::const_iterator areaIt = m_areas.begin(); areaIt != m_areas.end(); ++areaIt)
	{
		WorldArea* area = *areaIt;

		const WorldArea::SectorsCollection& sectors = area->getSectors();
		WorldArea::SectorsCollection::const_iterator sectorsEnd = sectors.end();

		for (WorldArea::SectorsCollection::const_iterator sectorIt = sectors.begin(); sectorIt != sectorsEnd; ++sectorIt)
		{
			WorldSector* sector = *sectorIt;

			sector->age = sector->age + 1;

			if (!sector->isInLru && sector->age > 1)
			{
				sector->isInLru = true;
				m_lruSectors.push_back(sector);
			}
		}
	}

	for (AreasOfInterestCollection::const_iterator areaIt = m_areasOfInterest.begin(); areaIt != m_areasOfInterest.end(); ++areaIt)
	{
		(*areaIt)->getAABBox(areaBoundsCells);

		areaBoundsCells.min.x = std::max(-m_planet->getRadius(), areaBoundsCells.min.x);
		areaBoundsCells.min.y = std::max(m_planet->getMaxDepth(), areaBoundsCells.min.y);
		areaBoundsCells.min.z = std::max(-m_planet->getRadius(), areaBoundsCells.min.z);

		areaBoundsCells.max.x = std::min(m_planet->getRadius() - 1, areaBoundsCells.max.x);
		areaBoundsCells.max.y = std::min(m_planet->getMaxAltitude() - 1, areaBoundsCells.max.y);
		areaBoundsCells.max.z = std::min(m_planet->getRadius() - 1, areaBoundsCells.max.z);

		sectorStart = WorldSector::getSectorIdFromCell(areaBoundsCells.min);
		sectorEnd = WorldSector::getSectorIdFromCell(areaBoundsCells.max) + Vector3i(1, 1, 1);

		for (dw::int32 k = sectorStart.z; k < sectorEnd.z; ++k)
		{
			sectorId.z = k;
			sectorBoundsCells.min.z = k * WorldSector::SideCells;
			sectorBoundsCells.max.z = k * WorldSector::SideCells + WorldSector::SideCells - 1;

			for (dw::int32 j = sectorStart.y; j < sectorEnd.y; ++j)
			{
				sectorId.y = j;
				sectorBoundsCells.min.y = j * WorldSector::SideCells;
				sectorBoundsCells.max.y = j * WorldSector::SideCells + WorldSector::SideCells - 1;

				for (dw::int32 i = sectorStart.x; i < sectorEnd.x; ++i)
				{
					worldArea = NULL;
					sector = NULL;

					sectorId.x = i;
					sectorBoundsCells.min.x = i * WorldSector::SideCells;
					sectorBoundsCells.max.x = i * WorldSector::SideCells + WorldSector::SideCells - 1;

					dw::float32 sectorPriority = std::numeric_limits< dw::float32 >::max();

					sectorPriority = std::min(sectorPriority, (*areaIt)->getPriority(sectorBoundsCells));

					if (needsSort)
					{
						std::sort(m_cache.begin(), m_cache.end(), SectorPriorityComp());
						needsSort = false;

						if (m_cache.size() >= MaxCachedSectorsPerUpdate)
						{
							lowestCachedPrio = m_cache.back()->priority;
						}
						else
						{
							lowestCachedPrio = std::numeric_limits< dw::float32 >::max();
						}
					}

					if (sectorPriority < lowestCachedPrio)
					{
						areaId = WorldArea::getAreaIdFromSector(sectorId);
						worldArea = getArea(areaId);

						dwAssert(worldArea != NULL, "Invalid area");

						sector = worldArea->getSector(sectorId);

						if (sector != NULL)
						{
							if (sector->isInLru)
							{
								dwLogInform("Reusing %i,%i,%i from lru\n", sector->getId().x, sector->getId().y, sector->getId().z);

								for (SectorsLRUQueue::iterator lruIt = m_lruSectors.begin(); lruIt != m_lruSectors.end(); ++lruIt)
								{
									if (sector == *lruIt)
									{
										m_lruSectors.erase(lruIt);
										break ;
									}
								}
							}
						}

						if (sector == NULL && m_cache.size() > 0)
						{
							for (SectorsCacheQueue::iterator cacheIt = m_cache.begin(); cacheIt != m_cache.end(); ++cacheIt)
							{
								if (sectorId == (*cacheIt)->getId())
								{
									sector = (*cacheIt);
									break ;
								}
							}

							if (sector != NULL)
							{
								dwLogInform("Sector %i,%i,%i is already in cacheQueue\n", sector->getId().x, sector->getId().y, sector->getId().z);
							}
						}

						if (sector == NULL && m_freeSectors.size() > 0)
						{
							sector = m_freeSectors.front();

							sector->setId(sectorId);
							sector->isCached = false;

							m_cache.push_back(sector);
							needsSort = true;

							if (sector != NULL)
							{
								dwLogInform("Sector %i,%i,%i from free\n", sector->getId().x, sector->getId().y, sector->getId().z);
							}

							m_freeSectors.pop();
						}

						if (sector == NULL && m_lruSectors.size() > 0)
						{
							sector = m_lruSectors.front();

							Vector3i lruAreaId = WorldArea::getAreaIdFromSector(sector->getId());

							WorldArea* lruArea = getArea(lruAreaId);
							lruArea->removeSector(sector);

							sector->setId(sectorId);
							sector->isCached = false;

							m_cache.push_back(sector);
							needsSort = true;

							if (sector != NULL)
							{
								dwLogInform("Sector %i,%i,%i from lru\n", sector->getId().x, sector->getId().y, sector->getId().z);
							}

							m_lruSectors.erase(m_lruSectors.begin());
						}

						// dwAssert(sector != NULL, "Can not find a free sector");

						if (sector != NULL)
						{
							sector->isInLru = false;
							sector->age = 0;
							sector->priority = std::min(sector->priority, sectorPriority);
						}
					}
				}
			}
		}
	}

	if ( m_cache.size() )
	{
		dwLogInform("--------------------------\n\n");
	}
	
	dw::uint32 cachedCount = 0;
	while (m_cache.size() > 0)
	{
		WorldSector* sector = m_cache.back();
		dwAssert(!sector->isCached, "Arf");

		if (cachedCount < MaxCachedSectorsPerUpdate)
		{
			areaId = WorldArea::getAreaIdFromSector(sector->getId());
			worldArea = getArea(areaId);

			sector->isCached = true;

			worldArea->addSector(sector);

			cachedCount++;
		}
		else
		{
			m_freeSectors.push(sector);
		}

		m_cache.pop_back();
	}
	*/
}

/*virtual*/ Service::State ServiceWorld::onTerminateSimulation()
{
	delete [] m_sectors;
	delete m_planet;

	return super::onTerminateSimulation();
}

void ServiceWorld::initializeAreas()
{
	/*
	dwAssert(m_planet != NULL, "Invalid internal State");

	dw::int32 planetRadius = m_planet->getRadius();
	dw::int32 areasCountRadius = planetRadius / WorldArea::SideCells;
	dw::int32 restCellsRadius = planetRadius - (areasCountRadius * WorldArea::SideCells);

	dw::int32 areasCountXZ = ( areasCountRadius + (restCellsRadius == 0 ? 0 : 1) );

	dw::int32 planetMaxAltitude = m_planet->getMaxAltitude();
	dw::int32 areasCountAltitude = planetMaxAltitude / WorldArea::SideCells;
	dw::int32 restCellsAltitude = planetMaxAltitude - (areasCountAltitude * WorldArea::SideCells);
	areasCountAltitude = areasCountAltitude + (restCellsAltitude == 0 ? 0 : 1);

	dw::int32 planetMaxDepth = std::abs(m_planet->getMaxDepth());
	dw::int32 areasCountDepth = planetMaxDepth / WorldArea::SideCells;
	dw::int32 restCellsDepth = planetMaxDepth - (areasCountDepth * WorldArea::SideCells);
	areasCountDepth = areasCountDepth + (restCellsDepth == 0 ? 0 : 1);

	dw::uint32 areasCount = (4 * areasCountXZ * areasCountXZ) * (areasCountAltitude + areasCountDepth);

	m_areasCount.x = 2 * areasCountXZ;
	m_areasCount.y = areasCountAltitude + areasCountDepth;
	m_areasCount.z = 2 * areasCountXZ;

	m_areas.reserve(areasCount);

	for (dw::int32 k = -areasCountXZ; k < areasCountXZ; ++k)
	{
		for (dw::int32 j = -areasCountDepth; j < areasCountAltitude; ++j)
		{
			for (dw::int32 i = -areasCountXZ; i < areasCountXZ; ++i)
			{
				WorldArea* area = new WorldArea();

				area->setId(Vector3i(i, j, k));

				m_areas.push_back(area);
			}
		}
	}
	*/
}

void ServiceWorld::initializeSectors()
{
	/*
	dwAssert(m_planet != NULL, "Invalid internal state");

	dw::int32 planetRadius = m_planet->getRadius();
	dw::int32 sectorsCountRadius = planetRadius / WorldSector::SideCells;
	dw::int32 restCellsRadius = planetRadius - (sectorsCountRadius * WorldSector::SideCells);

	dw::int32 sectorsCountXZ = ( sectorsCountRadius + (restCellsRadius == 0 ? 0 : 1) );

	dw::int32 planetMaxAltitude = m_planet->getMaxAltitude();
	dw::int32 sectorsCountAltitude = planetMaxAltitude / WorldSector::SideCells;
	dw::int32 restCellsAltitude = planetMaxAltitude - (sectorsCountAltitude * WorldSector::SideCells);
	sectorsCountAltitude = sectorsCountAltitude + (restCellsAltitude == 0 ? 0 : 1);

	dw::int32 planetMaxDepth = std::abs(m_planet->getMaxDepth());
	dw::int32 sectorsCountDepth = planetMaxDepth / WorldArea::SideCells;
	dw::int32 restCellsDepth = planetMaxDepth - (sectorsCountDepth * WorldSector::SideCells);
	sectorsCountDepth = sectorsCountDepth + (restCellsDepth == 0 ? 0 : 1);

	dw::uint32 sectorsCount = (4 * sectorsCountXZ * sectorsCountXZ) * (sectorsCountAltitude + sectorsCountDepth);

	m_sectorsCount = std::min(sectorsCount, MaxCachedSectors);
	m_sectors = new WorldSector[m_sectorsCount];

	for (dw::uint32 sectorIt = 0; sectorIt < m_sectorsCount; ++sectorIt)
	{
		WorldSector& sector = m_sectors[sectorIt];

		sector.setId(Vector3i(0, 0, 0));
		sector.isCached = false;
		sector.priority = std::numeric_limits< dw::float32 >::max();

		m_freeSectors.push(&m_sectors[sectorIt]);
	}

	m_cache.reserve(MaxCachedSectorsPerUpdate);
	*/
}

WorldArea* ServiceWorld::getArea(const Vector3i& areaId)
{
	dwAssert(areaId.x >= (-m_areasCount.x / 2), "Invalid parameter: areaId is out of range");
	dwAssert(areaId.x < (m_areasCount.x / 2), "Invalid parameter: areaId is out of range");
	dwAssert(areaId.y >= (-m_areasCount.y / 2), "Invalid parameter: areaId is out of range");
	dwAssert(areaId.y < (m_areasCount.y / 2), "Invalid parameter: areaId is out of range");
	dwAssert(areaId.z >= (-m_areasCount.z / 2), "Invalid parameter: areaId is out of range");
	dwAssert(areaId.z < (m_areasCount.z / 2), "Invalid parameter: areaId is out of range");

	dw::int32 areaIndex =
		(areaId.z + m_areasCount.z / 2) * (m_areasCount.y * m_areasCount.x) +
		(areaId.y + m_areasCount.y / 2) * m_areasCount.x +
		(areaId.x + m_areasCount.x / 2);

	dwAssert(areaIndex >= 0, "Invalid index");
	dwAssert(areaIndex < (dw::int32)m_areas.size(), "Invalid index");

	return m_areas[areaIndex];
}

} // namespace infinity
